package com.jiunuan.live.id.generate.provider.service.impl;

import com.jiunuan.live.id.generate.provider.dao.mapper.IdGenerateMapper;
import com.jiunuan.live.id.generate.provider.dao.po.IdGeneratePO;
import com.jiunuan.live.id.generate.provider.service.IdGenerateService;
import com.jiunuan.live.id.generate.provider.service.bo.LocalSeqIdBO;
import com.jiunuan.live.id.generate.provider.service.bo.LocalUnSeqIdBO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @Author JiuNuan
 * @Date: Created in 15:47 2024/8/1
 * @Description: id生成服务实现类
 */
@Service
@Slf4j
public class IdGenerateServiceImpl implements IdGenerateService, InitializingBean {
    private static final float UPDATE_RATE_75 = 0.75f; //id阈值常量
    private static final float UPDATE_RATE_25 = 0.25f; //id阈值常量
    private static final int SEQ_ID = 1;    //序列ID
    @Resource
    private IdGenerateMapper idGenerateMapper;
    /*
        定义一个线程安全的映射，用于存储整型键和LocalSeqIdBO对象的映射关系。
        使用ConcurrentHashMap保证在并发环境下安全地访问和修改。
        这个映射对于需要快速访问和存储序列ID对象的场景非常有用。
     */
    private static Map<Integer, LocalSeqIdBO> localSeqIdBOMap = new ConcurrentHashMap<>();
    private static Map<Integer, LocalUnSeqIdBO> localUnSeqIdBOMap = new ConcurrentHashMap<>();

    // 定义一个并发安全的映射，用于动态生成信号量
    // 键是整数类型，表示特定的资源标识；值是信号量，用于控制对特定资源的访问
    private static Map<Integer, Semaphore> semaphoreMap = new ConcurrentHashMap<>();

    // 创建一个静态的线程池执行器，专门用于处理线程池中的任务
    private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
        8, // 核心线程数，定义了在任何时刻可以同时执行的线程数量
        16, // 最大线程数，当任务数量激增时，可以临时增加的线程数量上限
        3, // 空闲线程存活时间，单位为秒，定义了线程在没有任务执行时，等待新任务的时间
        TimeUnit.SECONDS, // 时间单位，上述空闲线程存活时间的单位
        new ArrayBlockingQueue<>(1000), // 任务队列，定义了当所有线程都忙碌时，新任务的等待队列大小
        new ThreadFactory(){ // 线程工厂，用于创建新线程
            @Override
            public Thread newThread(Runnable r){
                Thread thread = new Thread(r);
                // 为每个新创建的线程设置一个唯一的名称，便于在多线程环境下识别和管理
                thread.setName("id-generate-thread-" + ThreadLocalRandom.current().nextInt(1000));
                return thread;
            }
        }
    );

    @Override
    public Long getSeqId(Integer id) {
        //参数校验
        if (id == null) {
            log.error("[getSeqId] id is error,id is {}", id);
            return null;
        }
        LocalSeqIdBO localSeqIdBO = localSeqIdBOMap.get(id);
        if (localSeqIdBO == null) {
            log.error("[getSeqId] localSeqIdBO is null,id is {}", id);
            return null;
        }
        //刷新本地内存中的id,异步任务
        this.refreshLocalSeqId(localSeqIdBO);
        long returnId = localSeqIdBO.getCurrentValue().incrementAndGet();
        if (returnId > localSeqIdBO.getNextThreshold()) {
            //同步去刷新 todo 考虑并发量
            log.error("[getSeqId] id is over limit,id is {}", id);
            return null;
        }
        return returnId;
    }

    /**
     * 刷新本地有序id段
     * @param localSeqIdBO
     */
    private void refreshLocalSeqId(LocalSeqIdBO localSeqIdBO) {
        // 计算当前ID段的使用情况，判断是否需要更新ID段
        long step = localSeqIdBO.getNextThreshold() - localSeqIdBO.getCurrentStart();
        // 如果已使用的ID数量超过当前段的75%，则更新本地内存中的ID段
        if(localSeqIdBO.getCurrentValue().get() - localSeqIdBO.getCurrentStart() > step * UPDATE_RATE_75){
            Semaphore semaphore = semaphoreMap.get(localSeqIdBO.getId());
            if(semaphore == null){
                log.error("semaphore is not null,id is {}", localSeqIdBO.getId());
                return;
            }
            boolean acquireStatus = semaphore.tryAcquire();
            if(acquireStatus){
                log.info("开始尝试进行本地Id段同步操作");
                threadPoolExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        try{
                            // 从数据库中查询当前的ID生成信息
                            IdGeneratePO idGeneratePO = idGenerateMapper.selectById(localSeqIdBO.getId());
                            // 尝试更新数据库中的ID生成记录
                            tryUpdateMySQLRecord(idGeneratePO);
                        }catch (Exception e){
                            log.error("[refreshLocalSeqId] error is ", e);
                        }finally {
                            semaphoreMap.get(localSeqIdBO.getId()).release();
                            log.info("本地有序id段同步完成,id is {}", localSeqIdBO.getId());
                        }
                    }
                });
            }
        }
    }

    @Override
    public Long getUnSeqId(Integer id) {
        if (id == null) {
            log.error("[getSeqId] id is error,id is {}", id);
            return null;
        }
        LocalUnSeqIdBO localUnSeqIdBO = localUnSeqIdBOMap.get(id);
        if (localUnSeqIdBO == null) {
            log.error("[getUnSeqId] localUnSeqIdBO is null,id is {}", id);
            return null;
        }
        Long returnId = localUnSeqIdBO.getIdQueue().poll();
        if (returnId == null) {
            log.error("[getUnSeqId] returnId is null,id is {}", id);
            return null;
        }
        this.refreshLocalUnSeqId(localUnSeqIdBO);
        return returnId;
    }
    /**
     * 刷新本地无序id段
     * @param localUnSeqIdBO
     */
    private void refreshLocalUnSeqId(LocalUnSeqIdBO localUnSeqIdBO){
        long begin = localUnSeqIdBO.getCurrentStart();
        long end = localUnSeqIdBO.getNextThreshold();
        long remainSize = localUnSeqIdBO.getIdQueue().size();
        // 如果剩余的ID数量小于当前段的25%，则更新本地内存中的ID段
        if(remainSize < (end - begin) * UPDATE_RATE_25){
            Semaphore semaphore = semaphoreMap.get(localUnSeqIdBO.getId());
            if (semaphore == null) {
                log.error("semaphore is null,id is {}", localUnSeqIdBO.getId());
                return;
            }
            boolean acquireStatus = semaphore.tryAcquire();
            if (acquireStatus) {
                threadPoolExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            IdGeneratePO idGeneratePO = idGenerateMapper.selectById(localUnSeqIdBO.getId());
                            tryUpdateMySQLRecord(idGeneratePO);
                        } catch (Exception e) {
                            log.error("[refreshLocalUnSeqId] error is ", e);
                        } finally {
                            semaphoreMap.get(localUnSeqIdBO.getId()).release();
                            log.info("本地无序id段同步完成，id is {}", localUnSeqIdBO.getId());
                        }
                    }
                });
            }
        }
    }
    /**
     * 初始化方法，在bean初始化完成后执行
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        /*
            在启动之前，将mysql的id配置初始化到本地内存中
         */
        List<IdGeneratePO> idGeneratePOList = idGenerateMapper.selectAll();
        // 遍历ID构建器列表，尝试为每个构建器更新版本号
        for (IdGeneratePO idGeneratePO : idGeneratePOList) {
            log.info("服务刚启动，抢占新的id段");
            /*
                尝试更新MySQL中的记录。这个步骤是为了确保ID生成的持久化，以及避免并发问题。
             */
            tryUpdateMySQLRecord(idGeneratePO);

            /* 在一个并发安全的Map中为当前ID生成器实例注册一个信号量。
               这样做是为了控制对ID生成器的并发访问，保证线程安全。
               参数:
                 idGeneratePO.getId(): 获取ID生成器的唯一标识符，用于作为信号量Map的键。
                 new Semaphore(1): 为ID生成器实例初始化一个只能有一个线程访问的信号量。
             */
            semaphoreMap.put(idGeneratePO.getId(), new Semaphore(1));
        }
    }

    /**
     * 更新mysql里面的分布式id的配置信息，占用相应的id段
     * 同步执行，很多的网络IO，性能较慢
     * @param idGeneratePO 包含ID生成信息的对象
     */
    private void tryUpdateMySQLRecord(IdGeneratePO idGeneratePO) {
        // 尝试更新数据库中的ID计数和版本信息
        int updateResult = idGenerateMapper.updateNewIdCountAndVersion(
                idGeneratePO.getId(),
                idGeneratePO.getVersion());
        if (updateResult > 0) {
            // 更新成功，处理后续业务逻辑
            localIdBOHandler(idGeneratePO);
            return;
        }
        // 重试进行更新，最多尝试3次
        for (int i = 0; i < 3; i++) {
            // 重新获取最新的ID生成信息
            idGeneratePO = idGenerateMapper.selectById(idGeneratePO.getId());
            // 再次尝试更新数据库中的ID计数和版本信息
            updateResult = idGenerateMapper.updateNewIdCountAndVersion(
                    idGeneratePO.getId(),
                    idGeneratePO.getVersion());

            if (updateResult > 0) {
                // 更新成功，处理后续业务逻辑
                localIdBOHandler(idGeneratePO);
                return;
            }
        }
        // 所有重试均失败，抛出异常
        throw new RuntimeException("表id段占用失败，竞争过于激烈，id is " + idGeneratePO.getId());
    }

    /**
     * 专门处理如何将本地ID对象放入到Map中，并且进行初始化的
     * @param idGeneratePO 包含ID生成信息的对象
     */
    private void localIdBOHandler(IdGeneratePO idGeneratePO) {
        long currentStart = idGeneratePO.getCurrentStart();
        long nextThreshold = idGeneratePO.getNextThreshold();
        long currentNum = currentStart;
        if (idGeneratePO.getIsSeq() == SEQ_ID) {
            // 序列ID
            LocalSeqIdBO localSeqIdBO = new LocalSeqIdBO();
            AtomicLong atomicLong = new AtomicLong(currentNum);
            localSeqIdBO.setId(idGeneratePO.getId());
            localSeqIdBO.setCurrentValue(atomicLong);
            localSeqIdBO.setCurrentStart(currentStart);
            localSeqIdBO.setNextThreshold(nextThreshold);
            localSeqIdBOMap.put(localSeqIdBO.getId(), localSeqIdBO);
        }else{
            // 非序列ID
            LocalUnSeqIdBO localUnSeqIdBO = new LocalUnSeqIdBO();
            localUnSeqIdBO.setCurrentStart(currentStart);
            localUnSeqIdBO.setNextThreshold(nextThreshold);
            localUnSeqIdBO.setId(idGeneratePO.getId());
            long begin = localUnSeqIdBO.getCurrentStart();
            long end = localUnSeqIdBO.getNextThreshold();
            List<Long> idList = new ArrayList<>();
            for(long i = begin;i<end;i++){
                idList.add(i);
            }
            //将本地id段提前打乱，然后放入到队列中
            Collections.shuffle(idList);
            ConcurrentLinkedQueue<Long> idQueue = new ConcurrentLinkedQueue<>();
            idQueue.addAll(idList);
            localUnSeqIdBO.setIdQueue(idQueue);
            localUnSeqIdBOMap.put(localUnSeqIdBO.getId(), localUnSeqIdBO);
        }
    }
}
